<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
<html>
<head>
<title>Custom Widgets in PySide</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="PySide, custom widgets, tutorial, Python, Linux, GUI, learn PySide">
<meta name="description" content="This part of the PySide tutorial covers custom widgets.">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container2">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content2">


<a href="/" title="Home">Home</a>&nbsp;
<a href=".." title="Home">Contents</a>


<h1>Custom Widgets in PySide</h1>


<p>
PySide is rich on various widgets. No toolkit can provide all widgets
that programmers might need in their applications.  Toolkits usually provide only
the most common widgets like buttons, text widgets, sliders etc. 
If there is a need for a more specialized widget, we must create it ourselves. 
</p>

<div class="big_hor">
<script type="text/javascript"><!--
google_ad_client = "ca-pub-9706709751191532";
/* big_horizontal */
google_ad_slot = "2904953388";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<p>
Custom widgets are created by using the drawing tools provided by the toolkit. 
There are two possibilities. A programmer can modify
or enhance an existing widget. Or he can create a custom widget from scratch.
</p>


<h2>Burning widget</h2>

<p>
This is a widget that we can see in Nero, K3B or other CD/DVD burning software. 
We create the widget completely from scratch. It is based on a minimal 
<code>QtGui.QWidget</code> widget. 
</p>


<pre class="code">
#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PySide tutorial 

In this example, we create a custom widget.

author: Jan Bodnar
website: zetcode.com 
last edited: August 2011
"""

import sys
from PySide import QtGui, QtCore

class Communicate(QtCore.QObject):
    
    updateBW = QtCore.Signal(int)

class BurningWidget(QtGui.QWidget):
  
    def __init__(self):      
        super(BurningWidget, self).__init__()
        
        self.initUI()
        
    def initUI(self):
        
        self.setMinimumSize(1, 30)
        self.value = 75
        self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]        


    def setValue(self, value):

        self.value = value


    def paintEvent(self, e):
      
        qp = QtGui.QPainter()
        qp.begin(self)
        self.drawWidget(qp)
        qp.end()
      
      
    def drawWidget(self, qp):
      
        font = QtGui.QFont('Serif', 7, QtGui.QFont.Light)
        qp.setFont(font)

        size = self.size()
        w = size.width()
        h = size.height()

        step = int(round(w / 10.0))


        till = int(((w / 750.0) * self.value))
        full = int(((w / 750.0) * 700))

        if self.value >= 700:
            qp.setPen(QtGui.QColor(255, 255, 255))
            qp.setBrush(QtGui.QColor(255, 255, 184))
            qp.drawRect(0, 0, full, h)
            qp.setPen(QtGui.QColor(255, 175, 175))
            qp.setBrush(QtGui.QColor(255, 175, 175))
            qp.drawRect(full, 0, till-full, h)
        else:
            qp.setPen(QtGui.QColor(255, 255, 255))
            qp.setBrush(QtGui.QColor(255, 255, 184))
            qp.drawRect(0, 0, till, h)


        pen = QtGui.QPen(QtGui.QColor(20, 20, 20), 1, 
            QtCore.Qt.SolidLine)
            
        qp.setPen(pen)
        qp.setBrush(QtCore.Qt.NoBrush)
        qp.drawRect(0, 0, w-1, h-1)

        j = 0

        for i in range(step, 10*step, step):
          
            qp.drawLine(i, 0, i, 5)
            metrics = qp.fontMetrics()
            fw = metrics.width(str(self.num[j]))
            qp.drawText(i-fw/2, h/2, str(self.num[j]))
            j = j + 1

class Example(QtGui.QWidget):
    
    def __init__(self):
        super(Example, self).__init__()
        
        self.initUI()
        
    def initUI(self):      

        sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)
        sld.setFocusPolicy(QtCore.Qt.NoFocus)
        sld.setRange(1, 750)
        sld.setValue(75)
        sld.setGeometry(30, 40, 150, 30)

        self.c = Communicate()
        self.wid = BurningWidget()
        self.c.updateBW[int].connect(self.wid.setValue)        

        sld.valueChanged[int].connect(self.changeValue)
        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(self.wid)
        vbox = QtGui.QVBoxLayout()
        vbox.addStretch(1)
        vbox.addLayout(hbox)
        self.setLayout(vbox)
        
        self.setGeometry(300, 300, 390, 210)
        self.setWindowTitle('Burning widget')
        self.show()
        
    def changeValue(self, value):
             
        self.c.updateBW.emit(value)
        self.wid.repaint()
        
def main():
    
    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
</pre>

<p>
In our example, we have a <code>QtGui.QSlider</code> and a 
custom widget. The slider controls the custom widget. This widget 
shows graphically the total capacity of a medium and the free space available to us. 
The minimum value of our custom widget is 1, the maximum is 750. If we reach value 700, 
we begin drawing in red color. This normally indicates overburning.
</p>

<p>
The burning widget is placed at the bottom of the window. This is 
achieved using one <code>QtGui.QHBoxLayout</code> 
and one <code>QtGui.QVBoxLayout</code>
</p>

<pre class="explanation">
class BurningWidget(QtGui.QWidget):
  
    def __init__(self):      
        super(BurningWidget, self).__init__()
</pre>

<p>
The burning widget it based on the <code>QtGui.QWidget</code> widget.
</p>

<pre class="explanation">
self.setMinimumSize(1, 30)
</pre>

<p>
We change the minimum size (height) of the widget. The default value 
is a bit small for us.
</p>

<pre class="explanation">
font = QtGui.QFont('Serif', 7, QtGui.QFont.Light)
qp.setFont(font)
</pre>

<p>
We use a smaller font than the default one. That better suits our needs.
</p>


<pre class="explanation">
size = self.size()
w = size.width()
h = size.height()

step = int(round(w / 10.0))


till = int(((w / 750.0) * self.value))
full = int(((w / 750.0) * 700))
</pre>

<p>
We draw the widget dynamically. The greater the window, the greater 
the burning widget. And vice versa. That is why we must calculate the 
size of the widget onto which we draw the custom widget. The till 
parameter determines the total size to be drawn. This value comes 
from the slider widget. It is a proportion of the whole area. The 
full parameter determines the point, where we begin to draw in red 
color. Notice the use of floating point arithmetics. This is to 
achieve greater precision.
</p>

<p>
The actual drawing consists of three steps. We draw the yellow or 
red and yellow rectangle. Then we draw the vertical lines, which 
divide the widget into several parts. Finally, we draw the numbers, 
which indicate the capacity of the medium.
</p>

<pre class="explanation">
metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))
</pre>

<p>
We use font metrics to draw the text. We must know the width of the 
text in order to center it around the vertical line.
</p>

<pre class="explanation">
def changeValue(self, value):
          
    self.c.updateBW.emit(value)
    self.wid.repaint()
</pre>

<p>
When we move the slider, the changeValue() method is called. 
Inside the method, we send a custom updateBW signal with a parameter. 
The parameter is the current value of the slider. The value is later 
used to calculate the capacity of the Burning widget to be drawn. 
The custom widget is then repainted.
</p>

<img src="/img/gui/pyside/customwidget.png" alt="The Burning widget">
<div class="figure">Figure: The Burning widget</div>


<p>
In this part of the PySide tutorial, we created a custom widget. 
</p>

<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div>
<br> 


<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡ 
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified October 10, 2011  <span class="copyright">&copy; 2007 - 2013 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>

